Skip to content

Use Vector4 storage for tangents in SurfaceTool for consistency - #119145

Merged
Repiteo merged 1 commit into
godotengine:masterfrom
zeux:surfacetool-tangent-vector4
Jun 19, 2026
Merged

Use Vector4 storage for tangents in SurfaceTool for consistency#119145
Repiteo merged 1 commit into
godotengine:masterfrom
zeux:surfacetool-tangent-vector4

Conversation

@zeux

@zeux zeux commented May 1, 2026

Copy link
Copy Markdown
Contributor

What does this do

The interface SurfaceTool presents for tangents and all its usages work with tangent frames represented as 4 values: tangent direction and scalar orientation (+1 or -1).

However, internally it was represented as tangent+bitangent (mistakenly called binormal), which introduces additional conversions back & forth and is problematic for replacing the tangent space generation algorithm.

This change converts the internal storage and adjusts the calling code accordingly. Note that since SurfaceTool::set_tangent is exposed to scripts, I've kept the Plane interface there even though it's misleading to use this type here (it does not describe the type correctly, I can only imagine this was used because Vector4 wasn't exposed to scripts?)

Note: I considered changing SurfaceTool::set_tangent and all internal code to use Vector4 instead of Plane, and just keep a compatibility helper SurfaceTool::set_tangent_script or something along these lines for the Plane type. I omitted this because that makes the patch a little larger but let me know if this would be preferable.

Note about the bitangent flipping code: this was needed because there is a disagreement between MikkTSpace and Godot on face winding (CW vs CCW). As a result, MikkTSpace internally uses flipped face normals compared to what Godot expects. This is mostly benign - it does not affect internal math etc. - but it does mean that, when bitangents were reprojected, they would flip compared to the expected direction of cross product according to the orientation flag. Using the orientation flag directly produces the same result and does not require coordinate system based flipping.

Tested on a few meshes including MRP from #114508, using a patch to visualize bitangent in the shader to validate that this change does not change generated tangent frames.

Why do we need this

Everywhere else in Godot, tangent frames are a 4-component vector (although sometimes encoded in a Plane for some reason), so I'd argue this is just good for consistency, and it slightly shrinks the SurfaceTool::Vertex which slightly speeds up reindexing operations, in addition to making it easier to reason about data propagation through the pipeline.

But the actual reason why I need this is that I have patches that, 1) replace mikktspace.c based tangent generation with new meshoptimizer tangent generation, 2) fix the tangent splitting on mirrored UVs (#114508). These simultaneously improve tangent quality and make tangent space generation ~6-7x faster on import, and using Vector4 in SurfaceTool helps maximize performance there as we no longer need to reconstruct bitangent just to later use it to recover the orientation. Since it is an independent cleanup that doesn't rely on a future version of meshoptimizer I figured I could submit that separately first.

@zeux
zeux requested review from a team as code owners May 1, 2026 16:14
The interface SurfaceTool presents for tangents and all its usages work
with tangent frames represented as 4 values: tangent direction and
scalar orientation (+1 or -1).

However, internally it was represented as tangent+bitangent (mistakenly
called binormal), which introduces additional conversions back & forth
and is problematic for replacing the tangent space generation algorithm.

This change converts the internal storage and adjusts the calling code
accordingly. Note that since SurfaceTool::set_tangent is exposed to
scripts, I've kept the Plane interface there even though it's misleading
to use this type here.
@zeux
zeux force-pushed the surfacetool-tangent-vector4 branch from d642e1f to d00e574 Compare May 1, 2026 16:17
@clayjohn clayjohn added this to the 4.8 milestone May 1, 2026
@clayjohn

clayjohn commented May 1, 2026

Copy link
Copy Markdown
Member

This change converts the internal storage and adjusts the calling code accordingly. Note that since SurfaceTool::set_tangent is exposed to scripts, I've kept the Plane interface there even though it's misleading to use this type here (it does not describe the type correctly, I can only imagine this was used because Vector4 wasn't exposed to scripts?)

Vector4 didn't exist in Godot until recently! For Godot 5 we will change the user-facing API to use Vector4 here as well

Note: I considered changing SurfaceTool::set_tangent and all internal code to use Vector4 instead of Plane, and just keep a compatibility helper SurfaceTool::set_tangent_script or something along these lines for the Plane type. I omitted this because that makes the patch a little larger but let me know if this would be preferable.

I don't have a problem with something like this. We have a similar pattern elsewhere. You could do something like:

  1. Change set_tangent() to accept a Vector4
  2. Add a set_plane() which just converts a plane to a Vector4 and calls set_tangent()
  3. Bind set_plane() as set_tangent() in the public facing API e.g. ClassDB::bind_method(D_METHOD("set_tangent", "tangent"), &SurfaceTool::set_plane);

@Repiteo
Repiteo merged commit fcfc285 into godotengine:master Jun 19, 2026
20 checks passed
@Repiteo

Repiteo commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Thanks!

BendyLand pushed a commit to BendyLand/voltaire that referenced this pull request Aug 2, 2026
…ector4

Use Vector4 storage for tangents in SurfaceTool for consistency
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants